/**
 * 注意Sound类是捕获异常只是在onLoad()失败的时候用
 * 
 * 这个的一个扩充的性能就是在没有addListener之前的事件保存，当addListern的时候再发出去
 * 
 * 注意我认为startLoad在启动的时候声音是0
 * 
 * 注意一个startLoad我认为在开始的时候是pause的，只有第一个字节加载近来了才去放
 * 
 * ------------
 * 这个没有严格测试过，要小心啊!
 * 
 * 注意初始化的条件，初始化的时候声音是0，默认是暂停的 
 * 
 * ~1105取消在这里做的超时处理
 * 
 *1114 	做好了seek,策略是pause着的时候就直接设置starPosi,playing的时候就pause,startPosi=seekTime,resum
*/
package kgame5.kmedia.xplayer {
import kgame5.kmath.MathDG;
import kgame5.kutil.*;

import flash.events.*;
import flash.media.*;
import flash.net.*;
import flash.utils.*;

public class MP3_XPlayer extends XPlayer{
	//执行声音淡出的操作的时间要是大于4秒，就直接把声音弄成０
	public static const MAX_FADEDUR:int=4*1000;
	
	public static const TIMEROUT:int=1*60*1000;//加载歌曲超时处理，单位是毫秒
	//__________________Property___________________
	//消息队列缓存，这个是为了处理在addListern之前发声了事件而用的，当addListern之后就把他们房到队列里
	private var fileUrl:String="";
	//
	private var sd : Sound = null;
	private var sdc:SoundChannel=null;
	private var sdt:SoundTransform=null;
	
	//在声音完全加载近来之前用来计算总时间长度
	private var isLoadOK:Boolean=false;
	
	//是否发生错误，发生了错误则在声音淡出的时候不设置sn.setVolumn(),getVol()里返回是0
	//在每次start（）的ns.play()之前设置为false,发生了错误设置为true
	private var isError:Boolean=false;
	
	//是否在播放
	private var isPlaying2 : Boolean = false;
	
	//播放完毕的时候sd.getVol,setVol里返回0,这个是为了处理在淡出中播放完毕
	private var isPlayComplete:Boolean=false;
	
	//--声音变淡的控制器
	private var timer_volFade:Timer;
	private var aimVol:Number;//目标音量
	
	//--
	private var startPosi:Number=0;//当启动播放的时候应该在哪里播放
	
	//--
	private var outerVolume:Number=70;//这个用来存储别外部设置的声音
	
	//隐藏声音，就是声音不受setVol()的控制
	private var bHideVolume:Boolean=true;
	
	//--超时处理
	private var timer:Timer;//加载的时候的超时处理
	
	private var id:int=0;
	private static var ID:int=0;//累加的，用来给每一个xplayer实例一个自己的编号
	//__________________Method___________________
	public  function MP3_XPlayer (p_fileUrl:String){
		fileUrl=p_fileUrl;
		id=ID++;
		
		trace2("new MP3_XPlayer");
		//--
		sd = new Sound ();
		
		sdt=new SoundTransform(1);//开始声音我默认是0,系统默认的是1
		outerVolume=1;
		//--
		aListener=new Array();
		
		//保存在addLisener之前缓存的消息
		aMsg=new Array();
		
		//--超时处理
		timer=new Timer(TIMEROUT);
		timer.addEventListener(TimerEvent.TIMER,th);
	}
	private function th(e:TimerEvent){//通知加载失败
		trace2("mp3xPlayer定时时间到，还没加载到，认为加载失败");
		
		 this.notifyPlayerLoadResult(false);
		//this.notifyPlayerLoadInit(false);
	}
	override public function pause():void{
		//T.p("pause");
		
		if(isPlaying2==false){//如果已经处在暂停状态就不要再暂停
			return;
		}
		
		isPlaying2=false;
		
		if(isLoadOK){//如果声音加载完毕
			//T.p("加载完毕");
			if(sdc!=null){//已经启动过播放了
				//T.p("sdc.stop!"+sdc.position);
				sdc.stop();
				startPosi=sdc.position;
				if(isNaN(startPosi)){
					startPosi=0;
				}
			}else{//sdc==null的时候是还没点sd.play()的时候，这个时候声音是静止的
				startPosi=0;
			}
		}else{//没加载完毕,就是把声音弄成0就可以了
			//T.p("没加载完毕");
			bHideVolume=true;
			sdt.volume=0;
			if(sdc!=null){//已经启动过播放了
				sdc.stop();
				startPosi=sdc.position;
				sdc.soundTransform=sdt;//真正把声音弄成0
			}else{//sdc==null的时候是还没点sd.play()的时候，这个时候声音是静止的
				startPosi=0;
			}
		}
	}
	
	//这个也是表示启动播放 
	override public function resume():void{
		
		//T.p("Resume");
		
		if(isPlaying2==true){//如果已经处在播放状态就不要再播放
			return;
		}
		isPlaying2=true;
		
		//--
		if(isLoadOK){//如果家在完毕
			//T.p("加载完毕");
		}else{//没加载完毕
			//T.p("没加载完毕");
			bHideVolume=false;
		}
		//--
		if(sdc!=null){
			sdc.removeEventListener(Event.SOUND_COMPLETE,playCompleteHandler);
		}
		
		//--
		try{
			sdc=sd.play(startPosi);
			sdt.volume=outerVolume;
			sdc.soundTransform=sdt;//设置声音音量
			sdc.addEventListener(Event.SOUND_COMPLETE,playCompleteHandler);
		}catch(err:Error){
			//这里抛出错误是可能的，比如(http://music.yobo.com/uu/20070913mp3/2254EM.mp3)
			//这个歌曲可以加载下来，但是在win mediaplayer里就说是无效的声音文件
			T.p("resume出现了问题!!", err.message);
			this.notifyPlayerError();
		}
	}
	//停止在开头
	override public function stop():void{
		//T.p("stop");
		pause();
		startPosi=0;
		//throw new Error("mp3_player.stop(),这个不用实现了，因为现在的这个没使用这个");
	}
	//
	override public function close():void{
		
		trace2("xplayer.close");
		
		timer.stop();//--超时记数要停下
		
		//--
		//先把声音停掉
		pause();
		//*
		//--
		if(sdc!=null){
			
			sdc.removeEventListener(Event.SOUND_COMPLETE,playCompleteHandler);
		}
		if(sd!=null){
			this.sd.removeEventListener(Event.COMPLETE, completeHandler);
		   
		    this.sd.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
		    this.sd.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, ioErrorHandler);
		    
		    this.sd.removeEventListener(ProgressEvent.PROGRESS, progressHandler);
		     
		    this.sd.removeEventListener(Event.ID3, id3Handler);
			
			
			if(this.isLoadOK==false){
				//T.p("关闭下载中的流");
				try{
					sd.close();//停止流，这个挺重要的
				}catch(er:Error){
					T.p("发生错误，不能关闭:",er);
				}
			}
		}
		//sdt=null;
		//sdc=null;
		//sd=null;
		//*/
	}
	//----------
	//跟播放池相关的
	
	override public function getFileUrl():String{
		//T.p("getFileUrl"+fileUrl);
		return fileUrl;
	}
	
	//////////////////
	override public function getState():int{
		return -1;//这里没有定义getState();
	}
	//启动加载,已经执行过加载了就不会再执行了,一个重要的注意是开始start的时候是默认关闭的，需要调用resume()来播放
	private var isFirstRun:Boolean=true;
	override public function startLoad ():void{
		
		trace2("xplayer.startLoad "+isFirstRun);
		if(isFirstRun){
			isFirstRun=false;
		}else{//这个是说start只执行一遍，因为start在外部和在OnLoadComplete()是都有可能执行,这个是资源池要用的
			//T.p("!已经执行过startLoad了");
			return;
		}
		
		//--
		isError=false;
		isPlayComplete=false;
		isPlaying2 = false;//默认是关闭的
		isLoadOK=false;
		
		//--
		/**!!很重要的用法，每次新的sound之前都要sd=new Sound(),sd.onLoad=function()定义一下
		否则在sd.loadSound()频繁调用的时候sd.onLoad里的得到的是false,导致了问题.连续多次
		*/
		
		if(checkFileUrl(fileUrl)){
			//--
		   this.sd.addEventListener(Event.OPEN, openHandler);
		    this.sd.addEventListener(Event.COMPLETE, completeHandler);
		   
		    this.sd.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
		   this.sd.addEventListener(SecurityErrorEvent.SECURITY_ERROR, ioErrorHandler);
		    
		    this.sd.addEventListener(ProgressEvent.PROGRESS, progressHandler);
		     
		    this.sd.addEventListener(Event.ID3, id3Handler);
		    
		    //--
		    var req:URLRequest = new URLRequest(this.fileUrl);
		    var context:SoundLoaderContext = new SoundLoaderContext(7*1000,false);//7秒
		    
		    T.p("sd.真正开始load");
		    sd.load(req, context);
		   
		    //--超时处理
		    timer.start();//开始记时
		    
		}else{
			
			trace2("xplayer.startLoad 可以提前判断无此mp3文件,可以马上通知加载失败");
			this.notifyPlayerLoadResult(false);
		}
	}
	//如果无实际歌曲mp3文件，地址最后为  "/empty.mp3"
	private function checkFileUrl(sUrl:String):Boolean{
		//empty.mp3,如果是9个
		if(sUrl.substr(-10,10).toLowerCase()=="/empty.mp3"){
			return false;//这个表示这个歌曲是没有的，可以提前判断加载失败了
		}
		return true;
	}
	
	private function openHandler(event:Event):void {
		
		trace2("xplayer.openHandler");
		//通知listener说第一个字节加载到
		//notifyPlayerLoadInit();
     }
	//
	private function completeHandler(event:Event):void {
		trace2("xplayer.completeHandler");
		
		bHideVolume=false;//家在完毕就不许要使用隐藏声音了
        
        this.notifyPlayerLoadResult(true);
     }
	
     private function ioErrorHandler(event:Event):void {
     
        trace2("xplayer.ioErrorHandler");
        this.notifyPlayerLoadResult(false);
     }

     private function progressHandler(event:ProgressEvent):void {
         //  T.p("progressHandler: " + event);
           if(getBytesLoaded()>0){
				//T.p("检测到下载了第一个字节 sd.duration:"+thisObj.sd.duration+" posi:"+thisObj.sd.position);
				
				//通知listener说第一个字节加载到
				notifyPlayerLoadInit();
				
				sd.removeEventListener(ProgressEvent.PROGRESS,progressHandler);
			}
     }
        
     private function id3Handler(event:Event):void {
        // T.p("id3Handler: " + event);
     }
     
     //--
     private function playCompleteHandler(event:Event):void{
     	trace2("xplayer.playCompleteHandler");
     	
     	notifyPlayComplete();
     }
	///////////////////////////////
	//
	override public function getBytesLoaded():int{
		return sd.bytesLoaded;
	}
	override public function getBytesTotal():int{
		return sd.bytesTotal;
	}
	override public function getLength():Number{
		return sd.length;
	};
	override public function getLoading():Number{
		//T.p("getLoading"+sd+" "+getBytesLoaded()+" "+getBytesTotal());
		if(getBytesTotal()==0){
			return 0;
		}
		var p:Number=getBytesLoaded()/getBytesTotal();
		
		return p;
	}
	//
	override public function getPosi():Number{
		var posi:Number=0;
		
		if(isPlaying2==false){//处在暂停状态的时候要返回startPosi.这个是为了处理seek()的时候在isPlayer2=false的时候也能正确显示进度
			posi=startPosi;
		}else{//处在播放状态的时候根据情况决定
			
			if(isLoadOK){//已经下载完了
				if(sdc!=null){
					posi=sdc.position;
			
					if(isNaN(posi)){//还没下载下来呢，要返回0
						posi=0;
					}
				}else{
					posi=0;
				}
				
			}else{//没下载完
				if(bHideVolume){//这个只是在暂停的时候才这样
					posi=this.startPosi;
				}else{
					if(sdc!=null){
						posi=sdc.position;
				
						if(isNaN(posi)){//还没下载下来呢，要返回0
							posi=0;
						}
					}else{
						posi=0;
					}
				}
			}
		}
		
		return posi;
	}
	//
	override public function getDur():Number{
		var dur:Number=sd.length;
		
		if(isNaN(dur)){
			return 0;
		}
		
		if(this.isLoadOK){//完全下载了就是真正的时间
			
		}else{//没下载完毕就用评估的时间
			
			var loading:Number=getLoading();
			
			if(loading==0){
				return 0;
			}else{
				dur=Math.floor(dur/loading);
			}
		}
		
		return dur;
	}
	//取得播放的进度
	override public function getPlaying():Number{
		//T.p("getPlaying "+sd.length+" "+sdc.position);
		var dur:Number=getDur();
		if(dur==0){
			return 0;
		}
		
		var posi:Number=getPosi();
		
		var p:Number= posi/dur;
		
		return p;
	}
	
	//
	override public function isPlaying () : Boolean{
		return isPlaying2;
	}
	
	//注意这个参数是秒数
	override public function seek(time:Number):void{
		if(isPlaying2){
			pause();
			startPosi=time;
			resume();
		}else{
			startPosi=time;
		}
	}
	
	//
	override public function getVol () : Number{
		//T.p("getVol");
		if(isError||isPlayComplete){//出现了错误则返回声音0,这个是为声音淡出而做的
			//T.p("返回了被",isError,isPlayComplete);
			return 0;
		}
		return outerVolume;
	}
	//设置声音,
	override public function setVol(p_volume : Number):void{
		//T.p("setVol",p_volume);
		if(isError||isPlayComplete){//出现错误则不允许设置声音了
			//T.p("返回了被",isError,isPlayComplete);
			return;
		}
		outerVolume=p_volume;
		
		if(bHideVolume){//这个时候sdt.voumne==0，不用去管
			
		}else{//不需要hideVolumen
			sdt.volume=p_volume;
			if(sdc!=null){
				sdc.soundTransform=sdt;
			}
		}
	}
	
	//------声音变淡------
	//注意考虑到在我淡出的时候收到pasue()命令的情况，这个情况是可能的，因为EI外部会调用我
	
	override public function startFadeout():void{
		//T.p("!!_________________Player2启动声音淡出");
		this.createVolumnFaceTimer(0);
	}
	//
	//声音淡入，从0~指定目标
	override public function startFadeIn(p_aimVol:Number):void{
		//T.p("!!_________________MP3_Player启动声音淡入");
		this.setVol(0);
		
		this.createVolumnFaceTimer(p_aimVol);
		
	}
	private function createVolumnFaceTimer(p_aimVol:Number):void{
		//T.p("createVolumnFaceTimer",p_aimVol);
		aimVol=p_aimVol;
		
		if(timer_volFade!=null){
			timer_volFade.removeEventListener(TimerEvent.TIMER,volumeFadeHandler);
			timer_volFade.stop();
			timer_volFade=null;
		}
		
		timer_volFade=new Timer(100);
		
		timer_volFade.addEventListener(TimerEvent.TIMER,volumeFadeHandler);
		timer_volFade.start();
		
	}
	private function volumeFadeHandler(e:TimerEvent):void{
		//
		var vol:Number=getVol();
		//T.p("volumeFadeHandler:"+vol);
		if(vol==aimVol || 
			this.isPlayComplete ||
			this.isError ||
			timer_volFade.currentCount>4*(1000/100)//如果超过4秒
			){
			//T.p("volumeFade OK!");
			timer_volFade.removeEventListener(TimerEvent.TIMER,volumeFadeHandler);
			timer_volFade.stop();
			timer_volFade=null;
			
			return;
		}
		
		//!!这里是特殊用法，注意vol和aimVol都是0~1了，所以用Spring的时候把他们扩大100倍
		vol=MathDG.Spring(vol*100,aimVol*100,3);
		setVol(vol/100);
		
		//--
		if(vol==0){
			//！！这个是非常重要的一点考虑，否则在淡出完毕（音乐卡片退出舞台后），当播放完毕还是要发送"歌曲听完了"的请求的，从而造成了问题，
			pause();
			//thisObj.close();//淡出完毕就关闭,!不能这样，因为还有东西在等待淡出完毕的通知,如果此时sd==null则无法取的声音
		}	
	}
	//
	private var aListener:Array=null;
	
	//保存在addListenr之前的发生的射箭
	private var aMsg:Array=null;
	
	override public function addListener(pl:IXPlayerListener):void{
		//T.p3("Mp3Player:"+getName()+" 添加了一个lisntern");
		var i:int;
		var len:int=aListener.length;
		for(i=0;i<len;i++){
			if(aListener[i]==pl){
				//T.p3("有相同的就不再添加了");
				return ;
			}
		}
		
		aListener.push(pl);
		
		//把在addListenr之前发生的事件广播给此listenr
		//T.p3("添加后分发");
		len=aMsg.length;
		for(i=0;i<len;i++){
			var msg:String=aMsg[i];
			switch(msg){
				case "OnPlayerLoadResult_true":
				//T.p("添加listerner后分发: loadResult:true");
				pl.OnPlayerLoadResult(true);
				break;
				case "OnPlayerLoadResult_false":
				//T.p("添加listerner后分发: loadResult:false");
				pl.OnPlayerLoadResult(false);
				break;
				case "OnPlayerLoadInit":
				//T.p("添加listerner后分发: loadInit");
				pl.OnPlayerLoadInit();
				break;
				
				case "OnPlayComplete"://这个其实在SmartPlayerPoll里是用不到的
				//T.p("添加listerner后分发: playComplete");
				pl.OnPlayComplete();
				break;
				case "OnPlayerError"://sdc.play()出现了错误
				//T.p("添加listerner后分发: OnPlayerError");
				pl.OnPlayerError();
				break;
					
			}
		}
	}
	
	
	
	private function notifyPlayerLoadResult(success:Boolean):void{
		//T.p("xplayer_________notifyPlayerLoadResult",success);
		
		timer.stop();//--超时记数要停下
		
		if(success==false){
			//T.p("加载失败",fileUrl);
			isError=true;
			
			 close();
			 
		}else {
			
			isLoadOK=true;
		}
		
		//T.p3("notifyPlayerLoadResult:"+success+" "+getName());
		
		if(aListener.length==0){//在没有添加listener之前就要把消息缓存
			//T.p3("loadResult放入消息缓存");
			aMsg.push("OnPlayerLoadResult_"+success);//加入消息队列
		}else{
			//T.p3("loadResutl 分发");
			var i:int;
			var len:int=aListener.length;
			for(i=0;i<len;i++){
				var l:IXPlayerListener=aListener[i];
				l.OnPlayerLoadResult (success);
			}
		}
	}
	
	private function notifyPlayerLoadInit():void{
		//T.p3("notifyPlayerLoadInit:"+getName());
		
		timer.stop();//--超时记数要停下
		
		if(isPlaying2){
			//T.p("下载第一字节后启动播放");	
			this.resume();
		}else{
			//T.p("下载第一字节后暂停播放");
			this.pause();		
		}
				
		if(aListener.length==0){//在没有添加listener之前就要把消息缓存
			//T.p3("loadInit放入消息缓存");
			aMsg.push("OnPlayerLoadInit");//加入消息队列
		}else{
			//T.p3("loadInit分发");
			var i:int;
			var len:int=aListener.length;
			for(i=0;i<len;i++){
				var l:IXPlayerListener=aListener[i];
				l.OnPlayerLoadInit();;
			}
		}
	}
	//!!这个是很隐秘的东西，有一种可能是歌曲可以下载不出错误，但是sdc.play()的时候会出现错误
	private function notifyPlayerError(){
		T.p("notifyPlayerError!!",fileUrl);
		if(aListener.length==0){//在没有添加listener之前就要把消息缓存
			//T.p3("loadInit放入消息缓存");
			aMsg.push("OnPlayerError");//加入消息队列
		}else{
			//T.p3("loadInit分发");
			var i:int;
			var len:int=aListener.length;
			for(i=0;i<len;i++){
				var l:IXPlayerListener=aListener[i];
				l.OnPlayerError();;
			}
		}
	}
	private function notifyPlayComplete():void{
		isPlayComplete=true;
		//T.p3("notifyPlayComplete:"+getName());
		if(aListener.length==0){//在没有添加listener之前就要把消息缓存
			//T.p3("playerComplete放入消息缓存");
			aMsg.push("OnPlayComplete");//加入消息队列
		}else{
			//T.p3("playerComplete分发");
			var i:int;
			var len:int=aListener.length;
			for(i=0;i<len;i++){
				var l:IXPlayerListener=aListener[i];
				l.OnPlayComplete();;
			}
		}
	}
	
	//实现*****************IXPlayerListener的接口*************************
	override  public function OnPlayComplete(){
	}
	
	override public function OnPlayerLoadResult(success:Boolean){
		//T.p("MP3_XPlayer.OnPlayerLoadResult:");
		//被通知到上一首歌曲加载完毕,不需要额外的 pause函数，因为pause会有隐藏的故障
		//这个会根据当前自己是否被调用了pause而自动做
		//T.p3("启动预先加载:"+getName());
		startLoad();//被通知说自己的上一个加载完毕，不管成功还是失败，都说加载完了
	}
	
	override public function OnPlayerLoadInit(){ 
	}
	override public function OnPlayerError(){ 
	}
	
	//======================调试信息
	public function trace2(sTip:String){
		T.p(id+" ___________________"+sTip+"_____________________");
		T.p("id:"+id,"fileUrl:"+fileUrl);
	}
} //end of class
}